/*
 * Developer : Jack Matthews
 * Email: developer@droidprofessor.com
 * 
 * Copyright (C) 2010 http://droidprofessor.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.droidprofessor.licensegenerator.lib;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;


import android.content.Context;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import com.droidprofessor.licensegenerator.lib.Base64;


/**
 * Features that can be added to a license
 * <br/><br/>
 * <b>Note:</b> An instance should be created using getInstance() <u>not</u> LicenseLoader.INSTANCE
 * 
 * @author Jack Matthews
 *
 */
public enum Feature {
	DEVICE_ID("device_id") {

		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			try {
				TelephonyManager telephonyManager = 
					(TelephonyManager)cnx.getSystemService(Context.TELEPHONY_SERVICE);

				String deviceId = telephonyManager.getDeviceId();

				//if using the emulator make a fake one
				if(TextUtils.isEmpty(deviceId))
					deviceId = "123456789";

				return doSha1AndBase64Encryption(
						deviceId,
						salt
				);			} catch (Exception e) {
				throw new EnablingFeatureFailedException(e.getMessage());
			}
		}  
	},
	ANDROID_BOOT_ID("android_boot_id") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			try {
				
				String id = Settings.System.getString(cnx.getContentResolver(), Settings.System.ANDROID_ID);

				//if using the emulator make a fake one
				if(TextUtils.isEmpty(id))
					id = "987654321";

				return doSha1AndBase64Encryption(id,salt);
				
			} catch (Exception e) {
				throw new EnablingFeatureFailedException(e.getMessage());
			}
		}
	},
	/**
	 * Sets the license to expire at a given time
	 * 
	 * @param extras expiry in <b>days</b> from now 
	 * (The actual date of expiry is calculated on the server)
	 */
	EXPIRY("expiry") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			if(extras==null) throw new EnablingFeatureFailedException("This feature requires a String expiry to be passed as an extra");

			//expiry in days
			return extras[0];
		}
	},
	PHONE_NUMBER("phone_number") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			try {
				
				TelephonyManager mTelephonyMgr = (TelephonyManager) cnx.getSystemService(Context.TELEPHONY_SERVICE);
				String number = mTelephonyMgr.getLine1Number();

				//if using the emulator make a fake one
				if(TextUtils.isEmpty(number))
					number = "(123)4567890";		

				return doSha1AndBase64Encryption(
						number,
						salt
				);
				
			} catch (Exception e) {
				throw new EnablingFeatureFailedException(e.getMessage());
			}
		}
	},
	TIMESTAMP("timestamp") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			try {
				
				Calendar now = Calendar.getInstance( TimeZone.getTimeZone("UTC") );
				return now.getTime().toString();
				
			} catch (Exception e) {
				throw new EnablingFeatureFailedException(e.getMessage());
			}
		}
	},
	/**
	 * The users Joomla username
	 * 
	 * @param extras username
	 */
	USERNAME("username") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			if(extras==null) throw new EnablingFeatureFailedException("This feature requires a String username to be passed as an extra");
			return extras[0];
		}
	},
	/**
	 * The licensing system to use
	 * 
	 * @param extras licensing system
	 * 
	 * 
	 */
	LICENSING_SYSTEM("licensing_system") {
		@Override
		public String getFeature(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
			if(extras==null) throw new EnablingFeatureFailedException("This feature requires a String licensing_system to be passed as an extra");

			return extras[0];
		}
	},
	/**
	 * The application ID from the joomla site
	 * 
	 * @param extras application ID
	 */
	APPLICATION_ID("application_id") {
		@Override
		public String getFeature(Context cnx, String salt, String ... extras) throws EnablingFeatureFailedException {
			if(extras==null) throw new EnablingFeatureFailedException("This feature requires a String application ID to be passed as an extra");
			return extras[0];
		}
	};
	
	private String key;
	private String value = null;
	
	private Feature(String urlkey) {
		this.key = urlkey;
	}
	
	public String getKey() {
		return new String(key);
	}
	
	public String getValue() {
		return value;
	}

	
	/**
	 * Return the key an value in the 
	 * form key=value
	 */
	@Override
	public String toString() {
		return key+"="+value;
	}
	
	
	/**
	 * Enable a feature
	 * 
	 * @param cnx Android Context
	 * @param salt salt used for encryption
	 * @param extras [optional] the extra is required by some features that need additional String arguments
	 * @throws EnablingFeatureFailedException
	 */
	public void enable(Context cnx, String salt, String... extras) throws EnablingFeatureFailedException {
		this.value = getFeature(cnx, salt, extras);
	}
	
	
	/**
	 * @return true if the feature is enabled
	 */
	public static boolean isEnabled(Feature f) {
		return f.value==null?false:true;
	}
	
	
	
//	/**
//	 * Disables the feature
//	 */
//	public void disable() {
//		this.value = null;
//	}
	
	
	/**
	 * Sets the value for the attribute
	 * @param cnx Android Context
	 * @param salt
	 * @param extras an optional argument required by some features, for other null will suffice
	 * @return String
	 * 
	 */
	protected abstract String getFeature(Context cnx, String salt, String ... extras) throws EnablingFeatureFailedException;
	
	
	/**
	 * Determines if the given key exists as a valid Feature
	 * @param key the urlKey value
	 * @return true if exists or false otherwise
	 */
	public static boolean keyExists(String key) {
		for(Feature f : Feature.values()) {
			if(f.getKey().equals(key)) {
				return true;
			}
		}
		return false;
	}

	
	/**
	 * Returns a List of all enabled features
	 * 
	 * @return A List of all enables Features
	 * @see Feature#enable(Context, String)
	 * @see Feature#disable()
	 */
	public static List<Feature> getEnabledFeatures() {
		List<Feature> features = new ArrayList<Feature>();
		
		for(Feature f : Feature.values()) {
			if(f.getValue() != null) {
				features.add(f);
			}
		}
		
		return features;
	}
	
	
	/**
	 * Compare the Feature value to the supplied one
	 * 
	 * @param value the value to compare to
	 * @return boolean true on equal
	 */
	public boolean isEqual(String value) {
		return this.value.equals(value);
	}
	
	
	
	
	/**
	 * Perform encryption of the data
	 * 
	 * @param data the data to encrypt
	 * @param salt
	 * salt some extra random data to help further 
	 * randomize the IMEI, this can be any string.
	 * It should be fixed and <u>never</u> change between
	 * versions of your application unless you wish to prevent
	 * access to the newer version from users that have
	 * paid for older versions.
	 * 
	 * @return the 'Base64 SHA1 hash' of the 'data' and 'salt'
	 */
	private static String doSha1AndBase64Encryption(String data, String salt) {
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance("SHA-256");
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("this should never happen");
		}

		if (TextUtils.isEmpty(data)) {
			throw new IllegalStateException("Data to encrypt is empty - aborting!");
		}

		byte[] hashedData = digest.digest( (data+salt).getBytes() );
		
		return Base64.encodeBytes(hashedData);
	}
	
}
